package jvm

import (
	"fmt"
	"io/ioutil"
	"strconv"
	"time"

	"github.com/allegro/bigcache"
	"github.com/ethereum/go-ethereum/rlp"
	"github.com/golang/protobuf/proto"
	"github.com/pkg/errors"
	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/common/util"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/contract"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go"
	jvmComm "hundsun.com/hsl/hschain/contract/jvm/jvm.go/common"
	jvmtypes "hundsun.com/hsl/hschain/contract/jvm/types"
	ecom "hundsun.com/hsl/hschain/executor/common"
	"hundsun.com/hsl/hschain/protos"
	"hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/execute"
	"hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/store"
	"hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/extend/account"
	storeContract "hundsun.com/hsl/hschain/store/extend/contract"
)

var (
	log = logging.NewLog(logging.Info, "jvm", nil)
)

// Reg jvm合约注册
func Reg(cfg *config.PluginConfig) {
	height := uint64(0)
	if cfg != nil {
		if v, ok := cfg.Parameters["height"]; ok {
			h, err := strconv.ParseInt(v, 10, 64)
			if err == nil && h > 0 {
				height = uint64(h)
			}
		}
	}
	//耗资源操作的由false改为true
	//即只会Init()一次，后面执行完本次交易之后，再来交易时候共享之前的init资源
	contract.Register(jvmtypes.JvmName, NewJvmContract, true, false, height)
}

// Jvm Jvm结构体
type Jvm struct {
	db     db.Database
	jvmIns *jvm.JVM
	//本地缓冲
	cache *bigcache.BigCache
}

// Rollback jvm合约回滚操作
func (p *Jvm) Rollback(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	panic("implement me")
}

// NewJvmContract 新建JVM合约对象
func NewJvmContract() contract.Contract {
	return &Jvm{}
}

// IsCallContract 是否为调用合约操作
func IsCallContract(tx *ledger.Transaction) bool {
	if tx.ContractId != nil && tx.ContractId.Name != "" && tx.ContractId.Version != "" && tx.To != "" &&
		tx.Payload != nil && len(tx.Payload) > 0 {
		return true
	}
	return false
}

// IsContractManager 是否为合约管理员
func IsContractManager(address string, managerList []string) bool {
	if address == "" {
		return false
	}

	if len(managerList) == 0 {
		return true
	}

	for _, mgr := range managerList {
		if mgr == address {
			return true
		}
	}

	return false
}

// Init 初始化Jvm合约
func (p *Jvm) Init() {
	config := bigcache.Config{
		Shards:             16,
		LifeWindow:         time.Duration(600) * time.Second,
		CleanWindow:        time.Duration(5) * time.Second,
		MaxEntriesInWindow: 16,
		Logger:             log,
	}
	p.cache, _ = bigcache.NewBigCache(config)
	p.jvmIns = &jvm.JVM{
		Cache:  p.cache,
		Logger: log,
	}

	p.db = store.GetLedgerDB()
	if code, _ := p.db.Get(jvmtypes.GetBaseContractKey()); code == nil {
		p.initContractCode()
	}

	p.jvmIns.Start(p.db)
}

func (p *Jvm) initContractCode() {
	ct := &common.Contract{
		Address: jvmtypes.BaseContractAddr,
		ContractId: &common.ContractIdentifier{
			Name:    jvmtypes.BaseContractName,
			Version: jvmtypes.BaseContractVersion,
		},
	}
	//set code
	log.Infof("Store base contract")
	baseCtJarPath := config.GetStringConfigValue("chain.baseContractPath", "")
	if baseCtJarPath == "" {
		panic("chain.baseContractPath not set")
	}
	if !util.FileExists(baseCtJarPath) {
		panic("base contract package'" + baseCtJarPath + "' not exists")
	}
	code, err := ioutil.ReadFile(baseCtJarPath)
	if err != nil {
		log.Fatalf("base contract package read failed %s", err.Error())
	}
	ct.Code = code
	codeKV, err := storeContract.CodeKV(ct)
	if err != nil {
		log.Fatalf("create contract code kv failed %s", err.Error())
	}
	err = p.db.Set(codeKV.Key, codeKV.Value)
	if err != nil {
		log.Fatalf("store base contract code failed %s", err.Error())
	}
}

// Run 运行Jvm合约
func (p *Jvm) Run(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	tx := gtx.GetTx()
	//if gtx.GetTx().GetTo() != "" {
	//	gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_OTHER_REASON
	//	return nil, errors.Errorf("jvm invalid to address: %s", gtx.GetTx().GetTo())
	//}

	from := tx.GetFrom()
	acc := account.NewAccountDB(ctx)
	fromAccount, _ := acc.GetAccount(from)
	if fromAccount == nil {
		log.Warnf("from account is not exist, create new addr is %s", from)
		fromAccount = &common.Account{InternalAddress: from, Type: common.Account_USER}
		kv, _ := account.KV(fromAccount)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		// 这里payload内部存放当前变更之后的账户信息
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: types.TyLogGenerate, Data: kv.Value})
	}

	fee := tx.GetFee()
	genesisPayload := pb.GetGenesisTransactionPayload()
	if genesisPayload.GetFeeLimit() != nil && genesisPayload.GetFeeLimit().GetMinTxFee() > 0 {
		if fee < genesisPayload.GetFeeLimit().GetMinTxFee() {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FEE_NOT_ENOUGH
			return gtx, nil
		}
	}

	fromBalance := fromAccount.GetBalance()
	if fee > fromBalance {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_INSUFFICIENT_FUNDS
		return gtx, nil
	}

	defer func() {
		fromAccount.Balance -= fee
		kv, _ := account.KV(fromAccount)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: types.TyLogFee, Data: kv.Value})
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	}()

	//调用合约
	if IsCallContract(tx) {
		gtx.TxExecRst.Receipt.ContractAddress = tx.GetTo()
		gtx.TxExecRst.Receipt.ContractId = tx.ContractId
		err := p.runCallContract(ctx, gtx)
		if err != nil {
			return gtx, err
		}
		//合约执行结果为失败，需要回退交易执行时更改的状态
		if gtx.TxExecRst.Receipt.TxStatus != ledger.TransactionReceipt_SUCCESSFUL {
			gtx.VarsRWStatus = nil
			gtx.TxExecRst.Kvs = nil
		} else {
			for _, event := range gtx.TxExecRst.Receipt.Events {
				if event.ContractId == nil {
					event.ContractId = tx.ContractId
				}
			}
		}
	} else {
		if tx.GetTo() == "" {
			if len(tx.Payload) <= 6 && (string(tx.Payload) == "PAUSE" || string(tx.Payload) == "RESUME" ||
				string(tx.Payload) == "STOP") {
				//更改合约状态
				err := p.runUpdateContractState(ctx, gtx)
				return gtx, err
			}
			//创建合约
			contractAddress, err := p.runCreateContract(ctx, gtx)
			if err != nil {
				log.Errorf("Create contract failed,error is %s", err.Error())
				return gtx, nil
			}
			log.Infof("Create contract OK,new address is %s", contractAddress)
		}
	}

	return gtx, nil

}
func (p *Jvm) runCreateContract(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (string, error) {
	tx := gtx.GetTx()
	if tx.Payload == nil || len(tx.Payload) == 0 {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PAYLOAD_EMPTY
		return "", errors.New("create contract but payload is empty")
	}
	isUpgrade := false
	ctVer := &common.ContractVersion{}
	ctVerBytes, err := ctx.Get(storeContract.GenContractVersionKey(tx.ContractId.Name))
	if err != nil && err != types.ErrNotFound {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return "", err
	}

	//合约首次部署
	if ctVerBytes != nil {
		err := proto.Unmarshal(ctVerBytes, ctVer)
		if err != nil {
			return "", err
		}
		//合约已被停用或注销
		if ctVer.State != common.ContractVersion_NORMAL {
			log.Errorf("Contract  state is not normal!")
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_STATE_NOT_NORMAL
			return "", errors.New("contract state is not normal")
		}
		//新升级合约版本小于上一个合约版本
		if !jvmComm.ContractVersionCompare(tx.ContractId.Version, ctVer.LastVersion) {
			log.Errorf("Contract version %s is lower than last version %s", tx.ContractId.Version, ctVer.LastVersion)
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_VERSION_INVALID
			return "", errors.New("call lost efficacy contract")
		}

		//没有合约管理员权限不能升级合约
		if !IsContractManager(tx.From, ctVer.ManagerList) {
			log.Errorf("From account %s not have the contract %s manager permission when upgrade",
				tx.From, tx.ContractId.Name)
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_NO_MANAGER_PERMISSION
			return "", errors.New("From account have no contract manager permission")
		}
		isUpgrade = true
	}

	var param []interface{}
	err = rlp.DecodeBytes(tx.Payload, &param)
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_UNKNOWN_PAYLOAD
		return "", err
	}
	code := param[0].([]byte)
	//合约管理者列表
	var mgrs []string
	//合约升级不需要更新合约管理者列表
	if isUpgrade {
		mgrs = ctVer.ManagerList
	} else {
		mgrIfs := param[1].([]interface{})
		if len(mgrIfs) > 0 {
			for _, mgr := range mgrIfs {
				mgrs = append(mgrs, string(mgr.([]byte)))
			}
		}
	}
	var payLoadList []interface{}

	err = rlp.DecodeBytes(tx.Payload, &payLoadList)
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_UNKNOWN_PAYLOAD
		return "", errors.New("Decode rlp bytes to list failed " + err.Error())
	}
	code = payLoadList[0].([]byte)
	//Check合约代码是否合法
	ct := common.Contract{Code: code}
	pass, rst, _ := p.jvmIns.CheckContract(code)
	if !pass {
		log.Errorf("Create contract failed,contract content checked failed,rst = %d", rst)
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_CODE_CHECK_INVALID
		return "", nil
	}
	//生成智能合约地址contractId+version
	newAddress := tx.GetContractAddressByID()
	sct := storeContract.NewContractCodeDB(ctx)
	ctNew, _ := sct.GetContractCode(newAddress)
	if ctNew != nil {
		//不能部署Name和Version都相同的contract
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CREATE_CONTRACT_ADDRESS_ERROR
		return "", errors.New("Contract already exist ")
	}

	//执行合约初始化
	ct.Address = newAddress
	ct.ContractId = tx.ContractId
	bytes, err := proto.Marshal(&ct)
	if err == nil && p.cache != nil {
		p.cache.Set(newAddress, bytes)
	}

	bl := p.jvmIns.BootLoader
	var mainClass string
	if bl != nil {
		mainClass = bl.GetMainContractClass(ct.Code)
		if mainClass == "" {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CALL_CONTRACT_ERROR
			return "", errors.New("main class not found")
		}
	} else {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CALL_CONTRACT_ERROR
		return "", errors.New("jvm bootloader is nil")
	}
	//调用jvm执行
	vmCtx := &jvmComm.VMRunContext{
		GroupedTx:       gtx,
		ExecCtx:         ctx,
		DeployInit:      true,
		ContractAddress: newAddress,
	}

	p.jvmIns.StartJVM8(mainClass, tx.Payload, log, vmCtx)

	//合约初始化失败
	if gtx.TxExecRst.Receipt.TxStatus != ledger.TransactionReceipt_SUCCESSFUL {
		if gtx.TxExecRst.Receipt.TxStatus == ledger.TransactionReceipt_SUCCESSFUL {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CREATE_CONTRACT_ADDRESS_ERROR
		}
		return newAddress, errors.New("Contract init  failed")
	}
	log.Infof("Create contract ok,new address %s", newAddress)
	gtx.TxExecRst.Receipt.ContractAddress = newAddress
	ctBytes, err := proto.Marshal(&ct)
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_OTHER_REASON
		return newAddress, errors.New("Internal error :proto marshal failed")
	}
	nskv := &common.KeyValue{Key: storeContract.GenContractCodeKey(newAddress), Value: ctBytes}
	gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, nskv)
	ctVer = &common.ContractVersion{
		Name:        tx.ContractId.Name,
		LastVersion: tx.ContractId.Version,
		State:       common.ContractVersion_NORMAL,
		ManagerList: mgrs,
	}

	kv, _ := storeContract.VersionKV(ctVer)
	gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
	ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)

	// update account kv
	var contractAddr = &common.Account{}
	contractAddr.InternalAddress = newAddress
	contractAddr.Type = common.Account_CONTRACT

	kv, _ = account.KV(contractAddr)
	ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)

	//创建合约账户，如果是升级合约，不需要创建
	if !isUpgrade {
		ctAccount := &common.Account{InternalAddress: newAddress,
			Balance: 0,
			Type:    common.Account_CONTRACT}
		kv, _ := account.KV(ctAccount)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	}
	return newAddress, nil
}

func (p *Jvm) runCallContract(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) error {
	tx := gtx.GetTx()

	if tx.ContractId.GetName() != tx.GetTo() {
		errInfo := errors.New(fmt.Sprint("call contract name is not same as contract id"))
		log.Error(errInfo.Error())
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PARAMETER_NOT_VALID
		return errInfo
	}

	ctAddr := tx.GetContractAddressByID()
	err := protos.Address(ctAddr).Validate()
	if err != nil {
		log.Errorf("validate contract address %s failed, err %s", tx.To, err.Error())
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PARAMETER_NOT_VALID
		return err
	}

	acc := account.NewAccountDB(ctx)
	ctAccount, err := acc.GetAccount(ctAddr)
	if err != nil {
		log.Errorf("Get contract account %s failed,err %s", tx.To, err.Error())
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return err
	}
	if ctAccount == nil || ctAccount.Type != common.Account_CONTRACT {
		//合约账户不存在
		if ctAccount == nil {
			log.Errorf("Contract account %s is not exist", tx.To)
		} else {
			log.Errorf("Contract account %s type is not contract", tx.To)
		}
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return errors.New("Contract account is not exist")
	}
	ctVerBytes, err := ctx.Get(storeContract.GenContractVersionKey(tx.ContractId.Name))
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return err
	}

	ctVer := &common.ContractVersion{}
	err = proto.Unmarshal(ctVerBytes, ctVer)
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_STATE_NOT_NORMAL
		return err
	}
	//合约已被停用或注销
	if ctVer.State != common.ContractVersion_NORMAL {
		log.Errorf("Contract  state is not normal!")
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_STATE_NOT_NORMAL
		return errors.New("contract  state is not normal")
	}
	// 校验调用的合约版本是否为最新版本
	if !jvmComm.ContractVersionCompare(tx.ContractId.Version, ctVer.LastVersion) {
		log.Errorf("Contract version %s is lower than last version %s", tx.ContractId.Version, ctVer.LastVersion)
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_LOST_EFFICACY
		return errors.New("call lost efficacy contract")
	}

	args := tx.Payload
	//获取智能合约代码
	//先查找本地cache
	var ctBytes []byte
	var ct *common.Contract
	sct := storeContract.NewContractCodeDB(ctx)
	if p.cache != nil {
		ctBytes, err = p.cache.Get(ctAddr)
	}
	if err != nil || len(ctBytes) == 0 {
		ct, err = sct.GetContractCode(ctAddr)
		if ct == nil || err != nil {
			log.Errorf("Get contract failed %v", err)
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
			return err
		}
		var bytes []byte
		bytes, err = proto.Marshal(ct)
		if err == nil && p.cache != nil {
			p.cache.Set(ctAddr, bytes)
		}
		//fmt.Println(hex.EncodeToString(ct.Code))
	} else {
		var result common.Contract
		err = proto.Unmarshal(ctBytes, &result)
		if err != nil {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_OTHER_REASON
			return err
		}
		ct = &result
	}
	if ct != nil {
		//合约状态不正常
		//mainClass := ct.ContractId.Name
		if ct.Code == nil || len(ct.Code) == 0 {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CALL_CONTRACT_ERROR
			return errors.New("contract code is nil")
		}
		bl := p.jvmIns.BootLoader
		var mainClass string
		if bl != nil {
			mainClass = bl.GetMainContractClass(ct.Code)
			if mainClass == "" {
				gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CALL_CONTRACT_ERROR
				return errors.New("main class not found")
			}
		} else {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CALL_CONTRACT_ERROR
			return errors.New("jvm bootloader is nil")
		}

		//调用jvm执行
		vmCtx := &jvmComm.VMRunContext{
			GroupedTx:       gtx,
			ExecCtx:         ctx,
			ContractAddress: ctAddr,
		}
		p.jvmIns.StartJVM8(mainClass, args, log, vmCtx)
		return nil
	}
	return errors.New("contract is not exsit")
}

// runUpdateContractState 更改合约状态
func (p *Jvm) runUpdateContractState(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) error {
	tx := gtx.Tx
	//获取智能合约地址contractId+version
	ctAddr := tx.GetContractAddressByID()
	ctNewState := string(tx.Payload)
	ctCodeBytes, err := ctx.Get(storeContract.GenContractCodeKey(ctAddr))
	if err != nil || ctCodeBytes == nil {
		log.Errorf("contract %s not exist when update state to %s", ctAddr, ctNewState)
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return errors.New("contract not exist")
	}
	oldState := common.ContractVersion_NORMAL
	newState := common.ContractVersion_NORMAL

	ctVerBytes, err := ctx.Get(storeContract.GenContractVersionKey(tx.ContractId.Name))
	ctVer := &common.ContractVersion{}
	err = proto.Unmarshal(ctVerBytes, ctVer)
	if err != nil {
		return err
	}
	//更新合约状态
	if ctNewState == "PAUSE" {
		if oldState == common.ContractVersion_NORMAL {
			newState = common.ContractVersion_PAUSED
		} else {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_STATE_NOT_NORMAL
			return errors.New("contract old state is not normal")
		}
	} else if ctNewState == "RESUME" {
		if oldState == common.ContractVersion_PAUSED {
			newState = common.ContractVersion_NORMAL
		} else {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_STATE_NOT_NORMAL
			return errors.New("contract old state is not paused")
		}
	} else if ctNewState == "STOP" {
		newState = common.ContractVersion_STOPED
	} else {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_CONTRACT_STATE_NOT_NORMAL
		return errors.New("not known contract state")
	}
	//状态没有发生改变
	if oldState == newState {
		return nil
	}

	ctVer.State = newState
	kv, _ := storeContract.VersionKV(ctVer)
	ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
	return nil
}
